/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2022.
 * Description: support mtrc feature
 * Author: liusongtang <liusongtang@huawei.com>
 * Create: 2020-10-22
 */

#include <asm/tlbflush.h>
#include <asm/pgtable.h>
#include <asm/io.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/list.h>
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/pid_namespace.h>
#include <linux/profile.h>
#if defined(CONFIG_ARM)
#include <asm/mach/map.h>
#endif
#include <linux/slab.h>
#include <linux/mtrc_pat.h>
#include <linux/rtos_pfn_valid.h>

static DEFINE_RWLOCK(mtrc_lock);

#define LINE_SIZE (80)
#define REGION_4G (0xffffffff)
#define MT_SHIFT  (2)

#define INVALID_TYPE (-1)

#if defined(CONFIG_ARM64)
#define HIGH_ATTR_MASK ((u64)0x3 << 54)	/* bit[55, 54] */
#define LOW_ATTR_MASK  (0x7FF)			/* bit[10, 0] */
#define PTE_MASK (_AT(pteval_t, 7) << 2)	/* bit[4, 2] */
#define MAX_MEM_PTE_TYPE_OLD	0x4
#define MAX_MEM_PTE_TYPE	0x6
#define MT_DEVICE_nGnRnE_OLD        0
#define MT_DEVICE_nGnRE_OLD         1
#define MT_DEVICE_GRE_OLD           2
#define MT_NORMAL_NC_OLD            3
#define MT_NORMAL_OLD               4
#elif defined(CONFIG_ARM)
#define HIGH_ATTR_MASK (L_PTE_XN | L_PTE_DIRTY | L_PTE_RDONLY)
#define LOW_ATTR_MASK  (0x7FF)			/* bit[10, 0] */
#endif

#define PID_CURRENT (-1)
#define PID_GLOBAL (-2)
#define SEARCH_ONLY_ME 0
#define SEARCH_INCLUDE_GLOBAL 1

struct mem_type_region_cfg {
	pid_t       pid;
	unsigned int type;
	phys_addr_t start;
	unsigned long len;
	pgprot_t attr;
#if defined(CONFIG_ARM64)
	pgprot_t input_attr;
#endif
	struct list_head mtrc;
};


LIST_HEAD(all_mtrc);

static int check_attr(pgprot_t attr)
{
#if defined(CONFIG_ARM64)
	pgprot_t memory_type = __pgprot((pgprot_val(attr) & PTE_MASK) >> MT_SHIFT);

	if (pgprot_val(attr) & (~(HIGH_ATTR_MASK | LOW_ATTR_MASK)))
		return -EINVAL;

	/* not allow memory type > MAX_MEM_PTE_TYPE_OLD */
	if (pgprot_val(memory_type) > MAX_MEM_PTE_TYPE_OLD)
		return -EINVAL;
#elif defined(CONFIG_ARM)
		pgprot_t memory_type = __pgprot((pgprot_val(attr) & L_PTE_MT_MASK) >> MT_SHIFT);
#ifdef CONFIG_ARM_LPAE
	if (pgprot_val(attr) & (~(HIGH_ATTR_MASK | LOW_ATTR_MASK)))
		return -EINVAL;

	/* not allow memory type:0x5, 0x6 */
	if (memory_type == 0x5 || memory_type == 0x6)
		return -EINVAL;
#else
	if (pgprot_val(attr) & (~LOW_ATTR_MASK))
		return -EINVAL;

	/* not allow memory type:0x5, 0xa, 0xd, 0xe, 0xf */
	if (memory_type == 0x5 || memory_type == 0xa || memory_type == 0xd ||
		memory_type == 0xe || memory_type == 0xf)
		return -EINVAL;
#endif
#endif
	return 0;
}

#if defined(CONFIG_ARM64)
static pgprot_t convert_memory_type(pgprot_t attr)
{
	unsigned int memory_type = (pgprot_val(attr) & PTE_MASK) >> MT_SHIFT;

	switch (memory_type) {
	case MT_DEVICE_nGnRnE_OLD:
		memory_type = MT_DEVICE_nGnRnE;
		break;
	case MT_DEVICE_nGnRE_OLD:
		memory_type = MT_DEVICE_nGnRE;
		break;
	case MT_DEVICE_GRE_OLD:
		memory_type = MT_DEVICE_GRE;
		break;
	case MT_NORMAL_NC_OLD:
		memory_type = MT_NORMAL_NC;
		break;
	case MT_NORMAL_OLD:
		memory_type = MT_NORMAL;
		break;
	}

	memory_type <<= MT_SHIFT;
	attr = __pgprot((pgprot_val(attr) & (~PTE_MASK)) | memory_type);

	return attr;
}
#endif

#define GLOBAL_PID_TRUE		1
#define GLOBAL_PID_FALSE	0
/* merge this two function */
static struct mem_type_region_cfg *search_list_mtrc(phys_addr_t start, unsigned long len, pid_t pid, int is_global)
{
	struct mem_type_region_cfg *mtrc_p;

	list_for_each_entry(mtrc_p, &all_mtrc, mtrc) {
		if ((is_global == GLOBAL_PID_FALSE && mtrc_p->pid != pid)
			|| (is_global == GLOBAL_PID_TRUE && mtrc_p->pid != PID_GLOBAL))
			continue;
		if (((mtrc_p->start <= start) && (mtrc_p->start + mtrc_p->len >= start + len))
			|| ((mtrc_p->start >= start) && (mtrc_p->start <= start + len))
			|| ((mtrc_p->start + mtrc_p->len >= start) && (mtrc_p->start + mtrc_p->len <= start + len)))
			return mtrc_p;
	}
	return NULL;
}

/* pid is enough, search_type is not necessary */
static struct mem_type_region_cfg *find_mtrc_without_lock(phys_addr_t start,
			unsigned long len, pid_t pid, int search_type)
{
	struct mem_type_region_cfg *mtrc_p;

	if (len <= 1)
	    return NULL;

	mtrc_p = search_list_mtrc(start, len - 1, pid, GLOBAL_PID_FALSE);
	if (mtrc_p != NULL)
		return mtrc_p;

	if (search_type == SEARCH_ONLY_ME || pid == PID_GLOBAL)
		return NULL;

	/* search global */
	mtrc_p = search_list_mtrc(start, len - 1, pid, GLOBAL_PID_TRUE);
	if (mtrc_p != NULL)
		return mtrc_p;

	return NULL;
}

int find_region_attr(phys_addr_t start_pfn, unsigned long len, unsigned int *type, pgprot_t *attr)
{
	struct mem_type_region_cfg *p;
	phys_addr_t start = __pfn_to_phys(start_pfn);
	pid_t pid = task_tgid_nr(current);

	read_lock(&mtrc_lock);
	p = find_mtrc_without_lock(start, len, pid, SEARCH_INCLUDE_GLOBAL);
	if (p) {
		if (start >= p->start && start + len - 1 <= p->start + p->len) {
			*attr = __pgprot(pgprot_val(p->attr));
			*type = p->type;
		} else {
			p = NULL;
		}
	}
	read_unlock(&mtrc_lock);

	if (!p)
		return -EINVAL;

	return 0;
}
EXPORT_SYMBOL(find_region_attr);

int is_mtrc_region_conflict(phys_addr_t start_pfn, unsigned long len)
{
	struct mem_type_region_cfg *p;
	phys_addr_t start = __pfn_to_phys(start_pfn);
		pid_t pid = task_tgid_nr(current);

	read_lock(&mtrc_lock);
	p = find_mtrc_without_lock(start, len, pid, SEARCH_INCLUDE_GLOBAL);
	read_unlock(&mtrc_lock);

	if (p) {
		if (start >= p->start && start + len - 1 <= p->start + p->len)
			return 0;
		else
			return -EINVAL;
	}

	return 0;
}
EXPORT_SYMBOL(is_mtrc_region_conflict);

#ifdef CONFIG_ARM64
#define LOG_RATELIMIT_INTERVAL_MSES (30 * 1000)

int track_pfn_remap(struct vm_area_struct *vma, pgprot_t *prot, unsigned long pfn,
	unsigned long addr, unsigned long size)
{
	static unsigned long j;

	if (!vma || !prot)
		return 0;

	if (!(pgprot_val(*prot) & PTE_NG) && printk_timed_ratelimit(&j, LOG_RATELIMIT_INTERVAL_MSES))
		WARN(1, "arm64/PAT: PTE_NG is not set in page prot\n");
	return 0;
}

void track_pfn_insert(struct vm_area_struct *vma, pgprot_t *prot, pfn_t pfn)
{
	static unsigned long j;

	if (!vma || !prot)
		return;

	if (!(pgprot_val(*prot) & PTE_NG) && printk_timed_ratelimit(&j, LOG_RATELIMIT_INTERVAL_MSES))
		WARN(1, "arm64/PAT: PTE_NG is not set in page prot\n");
}

int track_pfn_copy(struct vm_area_struct *vma) { return 0; }
void untrack_pfn(struct vm_area_struct *vma, unsigned long pfn, unsigned long size) {}
void untrack_pfn_clear(struct vm_area_struct *vma) {}

#endif

void mtrc_sync_vmflags(struct vm_area_struct *vma, unsigned long pfn, unsigned long len)
{
	unsigned int type;
	pgprot_t attr;
	int error;

	error = find_region_attr(pfn, (unsigned int)len, &type, &attr);

	if (error == 0 && pgprot_val(attr) != 0) {
		vma->vm_flags |= VM_READ;
#if defined(CONFIG_ARM64)
		if (pgprot_val(vma->vm_page_prot) & PTE_RDONLY) {
#elif defined(CONFIG_ARM)
		if (pgprot_val(vma->vm_page_prot) & L_PTE_RDONLY) {
#endif
			if (vma->vm_flags & VM_WRITE) {
#if defined(CONFIG_ARM64)
				pr_notice("[PAT]:mtrc set PTE_RDONLY, remove vm_flags`s VM_WRITE.\n");
#elif defined(CONFIG_ARM)
				pr_notice("[PAT]:mtrc set L_PTE_RDONLY, remove vm_flags`s VM_WRITE.\n");
#endif
				vma->vm_flags &= ~VM_WRITE;
			}
		} else {
			vma->vm_flags |= VM_WRITE;
		}
#if defined(CONFIG_ARM64)
		if (pgprot_val(vma->vm_page_prot) & PTE_UXN) {
#elif defined(CONFIG_ARM)
		if (pgprot_val(vma->vm_page_prot) & L_PTE_XN) {
#endif
			if (vma->vm_flags & VM_EXEC) {
#if defined(CONFIG_ARM64)
				pr_notice("[PAT]:mtrc NOT set PTE_EXEC, remove vm_flags`s VM_EXEC.\n");
				vma->vm_flags &= ~VM_EXEC;
#elif defined(CONFIG_ARM)
				pr_notice("[PAT]:mtrc set L_PTE_XN, remove vm_flags`s VM_EXEC.\n");
				vma->vm_flags &= ~VM_EXEC;
#endif
			}
		} else {
			vma->vm_flags |= VM_EXEC;
		}
#if defined(CONFIG_ARM64)
		if (vma->vm_start < TASK_SIZE)
			vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) | PTE_NG);

#endif
	}
}
EXPORT_SYMBOL(mtrc_sync_vmflags);

static int del_one_region(phys_addr_t start, unsigned long len, pid_t pid)
{
	struct mem_type_region_cfg *mtrc_p;
	phys_addr_t start_old;
	unsigned long len_old;
	int ret;

	ret = 0;

	write_lock(&mtrc_lock);
	mtrc_p = find_mtrc_without_lock(start, len + 1, pid, SEARCH_ONLY_ME);
	if (mtrc_p) {
		if (mtrc_p->start == start &&  mtrc_p->len == len) {
			list_del(&mtrc_p->mtrc);
			kfree(mtrc_p);
		} else {
			start_old = mtrc_p->start;
			len_old = mtrc_p->len;
			ret = -EBUSY;
		}
	} else {
		ret = -ENXIO;
	}

	write_unlock(&mtrc_lock);

	if (ret == -EBUSY)
		pr_err("[PAT]Error: new region [start=%llx len=%lx] conflicts with old region [start=%llx len=%lx].\n",
			(unsigned long long)start,
			len,
			(unsigned long long)start_old,
			len_old);

	if (ret == -ENXIO)
		pr_err("[PAT]Error: not exist region [start=%llx len=%lx], delete failed.\n",
				(unsigned long long)start, len);

	return ret;
}

static int add_region_with_mtrcp(struct mem_type_region_cfg *mtrc_p, phys_addr_t start, unsigned long len,
		pgprot_t attr)
{
	if (start == mtrc_p->start && len == mtrc_p->len && !pgprot_val(mtrc_p->attr) && pgprot_val(attr)) {
		mtrc_p->attr = __pgprot(pgprot_val(attr));
	} else {
		pr_err("[PAT]Error: new region [start=0x%llx len=0x%llx] conflicts with old region [start=0x%llx len=0x%llx].\n",
			(unsigned long long)start,
			(unsigned long long)len,
			(unsigned long long)mtrc_p->start,
			(unsigned long long)mtrc_p->len
			);
		return -EBUSY;
	}
	return 0;
}

static int add_region_without_mtrcp(phys_addr_t start, unsigned long len,
		    unsigned int type, pgprot_t attr, pid_t pid)
{
	struct mem_type_region_cfg *mtrc_p;
#if defined(CONFIG_ARM64)
	pgprot_t input_attr = attr;
#endif

	mtrc_p = kzalloc(sizeof(struct mem_type_region_cfg),  GFP_ATOMIC);
	if (!mtrc_p) {
		return -ENOMEM;
	} else {
#if defined(CONFIG_ARM64)
		attr = convert_memory_type(attr);

		pr_info("start=0x%0llx len=0x%llx pid=%d input_attr 0x%llx real_attr 0x%llx\n",
			(unsigned long long)start, (unsigned long long)len, pid,
			(unsigned long long)pgprot_val(input_attr),
			(unsigned long long)pgprot_val(attr));

		mtrc_p->input_attr = input_attr;
#endif
		mtrc_p->start = start;
		mtrc_p->len = len;
		mtrc_p->type = type;
		mtrc_p->attr = attr;
		mtrc_p->pid = pid;
		list_add_tail(&mtrc_p->mtrc, &all_mtrc);
	}
	return 0;
}

static int add_region(phys_addr_t start, unsigned long len, unsigned int type, pgprot_t attr, pid_t pid)
{
	int res = 0;
	struct mem_type_region_cfg *mtrc_p;

	write_lock(&mtrc_lock);

	mtrc_p = find_mtrc_without_lock(start, len, pid, SEARCH_ONLY_ME);
	if (mtrc_p)
		res = add_region_with_mtrcp(mtrc_p, start, len, attr);
	else
		res = add_region_without_mtrcp(start, len, type, attr, pid);

	write_unlock(&mtrc_lock);

	return res;
}

static int exit_pat(struct notifier_block *self, unsigned long val, void *data)
{
	struct mem_type_region_cfg *mtrc_p, *mtrc_tmp;
	pid_t pid = task_pid_nr((struct task_struct *)data);

	write_lock(&mtrc_lock);
	list_for_each_entry_safe(mtrc_p, mtrc_tmp, &all_mtrc, mtrc) {
		if (mtrc_p->pid == pid) {
			list_del(&mtrc_p->mtrc);
			kfree(mtrc_p);
		}
	}
	write_unlock(&mtrc_lock);

	return 0;
}

static struct notifier_block task_exit_nb = {
	.notifier_call  = exit_pat,
};

#ifdef CONFIG_PROC_FS
static int mtrc_show(struct seq_file *seq, void *offset)
{
	struct mem_type_region_cfg *mtrc_p;

	read_lock(&mtrc_lock);
	list_for_each_entry(mtrc_p, &all_mtrc, mtrc) {
		seq_printf(seq, "0x%llx - 0x%llx (",
			(unsigned long long)mtrc_p->start,
			(unsigned long long)(mtrc_p->start + mtrc_p->len));
#if defined(CONFIG_ARM)
		if (mtrc_p->type != INVALID_TYPE) {
			seq_printf(seq, "type:%d", mtrc_p->type);
			if (pgprot_val(mtrc_p->attr) != 0)
				seq_puts(seq, ", ");
		}
		if (pgprot_val(mtrc_p->attr) != 0)
			seq_printf(seq, "attr:0x%llx", (unsigned long long)pgprot_val(mtrc_p->attr));
#endif
#if defined(CONFIG_ARM64)
		if (pgprot_val(mtrc_p->attr) != 0)
			seq_printf(seq, "input_attr:0x%llx, real_attr:0x%llx",
			(unsigned long long)pgprot_val(mtrc_p->input_attr),
			(unsigned long long)pgprot_val(mtrc_p->attr));
#endif
		if (mtrc_p->pid != PID_GLOBAL)
			seq_printf(seq, ", pid:%d", mtrc_p->pid);

		seq_puts(seq, ")\n");
	}
	read_unlock(&mtrc_lock);

	return 0;
}

static int mtrc_open(struct inode *inode, struct file *file)
{
	return single_open(file, mtrc_show, NULL);
}

static int mtrc_get_pram(char *line, const char __user *buf, size_t len)
{
	size_t linelen;
	int length;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	if (len > LINE_SIZE - 1) {
		pr_err("[PAT]Error:input too long\n");
		return -EINVAL;
	}
	memset(line, 0, LINE_SIZE);

	length = len;
	length--;

	if (length < 0)
		return -EINVAL;

	if (copy_from_user((void *)line, buf, length))
		return -EFAULT;

	line[LINE_SIZE - 1] = '\0';
	linelen = strlen(line);
	if (linelen && linelen < LINE_SIZE) {
		if (line[linelen - 1] == '\n')
			line[linelen - 1] = '\0';
	} else {
		pr_err("[PAT]Error:Invalid input format:.\n");
		return -EINVAL;
	}
	return 0;
}

static int mtrc_get_pid(char **ptr, pid_t *pid)
{
	char *ep = NULL;
	char *tmp = *ptr;
	long tmp_pid;

	tmp = skip_spaces(tmp);
	if (tmp == NULL)
		return -EINVAL;
	if (strncmp(tmp, "pid=", 4)) { /* no pid= param means glabal */
		*pid = PID_GLOBAL;
		return 0;
	}

	tmp_pid = simple_strtol(tmp + 4, &ep, 0);
	if (ep == NULL || ep == (char *)(tmp + 4))
		return -EINVAL;
	if (tmp_pid < PID_GLOBAL)
		return -EINVAL;
	else if (tmp_pid == PID_CURRENT)
		tmp_pid = task_tgid_nr(current);
	*pid = tmp_pid;

	*ptr = ep;
	return 0;
}

static int mtrc_get_base(char **ptr, unsigned long long *base)
{
	char *ep = NULL;
	char *tmp = *ptr;

	tmp = skip_spaces(tmp);
	if (tmp == NULL || strncmp(tmp, "base=", 5))
		return -EINVAL;

	*base = simple_strtoull(tmp + 5, &ep, 0);
	if (ep == NULL || ep == (char *)(tmp + 5))
		return -EINVAL;

	*ptr = ep;
	return 0;
}

static int mtrc_get_size(char **ptr, unsigned long long *size)
{
	char *ep = NULL;
	char *tmp = *ptr;

	ep = skip_spaces(tmp);
	if (ep == NULL || ep == tmp || strncmp(ep, "size=", 5))
		return -EINVAL;

	tmp = ep;

	*size = simple_strtoull(tmp + 5, &ep, 0);
	if (ep == NULL || ep == (char *)(tmp + 5))
		return -EINVAL;

	*ptr = ep;
	return 0;
}

static int mtrc_check_base_size(unsigned long long base, unsigned long long size)
{
	unsigned long long last_addr;

	if ((base & (~PAGE_MASK)) || (size & (~PAGE_MASK))) {
		pr_err("[PAT]Error: base[0x%llx] or size[0x%llx] does not align by 0x%lx byte.\n",
					(unsigned long long)base,
					(unsigned long long)size,
					PAGE_SIZE);
		return -EINVAL;
	}

	if (size == 0 || size > REGION_4G) {
		pr_err("[PAT]Error: Invalid size[%llx].\n", (unsigned long long)size);
		return -EINVAL;
	}

	last_addr = base + size - 1;
	if (rtos_pfn_valid(__phys_to_pfn(base)) || rtos_pfn_valid(__phys_to_pfn(last_addr))
#ifdef PHYS_MASK
		|| (last_addr & ~PHYS_MASK)
#endif
		|| (last_addr < base)) {
		pr_err("[PAT]Error: Invalid region.base[%llx] - size:[%llx]\n",
					(unsigned long long)base,
					(unsigned long long)size);
		return -EINVAL;
	}

#if (!defined(CONFIG_ARM_LPAE) && defined(CONFIG_ARM))
	if (base + size - 1 > REGION_4G || base > REGION_4G || size - 1 > REGION_4G) {
		pr_err("[PAT]Error: input region base[%llx], size[%llx] is out of the range.\n",
				(unsigned long long)base, (unsigned long long)size);
		return -EINVAL;
	}
#endif

	return 0;
}

#define		MTRC_INPUT_UNKNOWN	0
#define		MTRC_INPUT_TYPE		1
#define		MTRC_INPUT_ATTR		2
#define		MTRC_INPUT_DELETE	3

static int mtrc_get_action(char *ptr, unsigned int *type, pgprot_t *attr)
{
	char *ep = NULL;

	ep = skip_spaces(ptr);
	if (ep == NULL || ep == ptr)
		return MTRC_INPUT_UNKNOWN;

	ptr = ep;
#if defined(CONFIG_ARM)
	if (strncmp(ptr, "type=", 5) == 0) {
		*type = simple_strtoll(ptr + 5, &ep, 0);
		if (ep == ptr + 5 || *ep != '\0')
			return MTRC_INPUT_UNKNOWN;

		return MTRC_INPUT_TYPE;
	} else if (strncmp(ptr, "attr=", 5) == 0) {
#elif defined(CONFIG_ARM64)
	if (strncmp(ptr, "attr=", 5) == 0) {
#endif
		*attr = __pgprot(simple_strtoll(ptr + 5, &ep, 0));
		if (ep == ptr + 5 || *ep != '\0')
			return MTRC_INPUT_UNKNOWN;

		return MTRC_INPUT_ATTR;
	} else if (strncmp(ptr, "delete", 6) == 0) {
		if (*(ptr + 6) != '\0')
			return MTRC_INPUT_UNKNOWN;

		return MTRC_INPUT_DELETE;
	}

	return MTRC_INPUT_UNKNOWN;
}

#if defined(CONFIG_ARM)
static int mtrc_check_type(unsigned int type)
{
	if (!get_mem_type(type))
		return -EINVAL;

	return !(MT_DEVICE == type || MT_UNCACHED == type || MT_WRITEALLOC_UNSHARE == type || MT_NOCACHE_UNSHARE == type);
}
#endif
/*
 * seq_file can seek but we ignore it.
 *
 * Format of control line:
 *    "base=%Lx size=%Lx type=%s" or "disable=%d"
 * or "pid=xxx base=%Lx size=%Lx type=%s", no pid= means global, pid=-1 means current
 */

static pgprot_t update_pg_attr(pgprot_t attr)
{
#if defined(CONFIG_ARM64)
	/* ensure some attr bit */
	attr = __pgprot(pgprot_val(attr) | PTE_TYPE_PAGE | PTE_AF | PTE_USER | PTE_SHARED);
	if (!(pgprot_val(attr) & PTE_RDONLY))
		attr = __pgprot(pgprot_val(attr) | PTE_DIRTY | PTE_WRITE);
	if (!(pgprot_val(attr) & PTE_PXN))
		attr = __pgprot(pgprot_val(attr) | PTE_PXN);
	if (!(pgprot_val(attr) & PTE_UXN))
		attr = __pgprot(pgprot_val(attr) | PTE_UXN);
#elif defined(CONFIG_ARM)
	attr = __pgprot(pgprot_val(attr) | L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_USER | L_PTE_SHARED);
	if (!(pgprot_val(attr) & L_PTE_RDONLY))
		attr = __pgprot(pgprot_val(attr) | L_PTE_DIRTY);
#endif
	return attr;
}

pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn,
				     unsigned long size, pgprot_t vma_prot)
{
	int check_mtrc;
	unsigned int mtrc_type;
	pgprot_t mtrc_attr;

	/* FPGA and SRAM etc device is uncachable and strong order */
	if (file->f_flags & O_DSYNC) {
		/* uncachable memory attibute */
		vma_prot =  pgprot_noncached(vma_prot);
	}

	check_mtrc = find_region_attr(pfn, size, &mtrc_type, &mtrc_attr);
#if defined(CONFIG_ARM64)
	if (!check_mtrc && pgprot_val(mtrc_attr))
		vma_prot = mtrc_attr;
#elif defined(CONFIG_ARM)
	if (check_mtrc == 0) {
		if (mtrc_attr != 0) {
			vma_prot = mtrc_attr;
		} else {
			switch (mtrc_type) {
			case MT_DEVICE:
				vma_prot = pgprot_device(vma_prot);
				break;
			case MT_DEVICE_NONSHARED:
				vma_prot = pgprot_device_noshare(vma_prot);
				break;
			case MT_STRONG_ORDER:
				vma_prot = pgprot_noncached(vma_prot);
				break;
			case MT_WRITEALLOC:
				vma_prot = pgprot_writealloc(vma_prot);
				break;
			case MT_WRITEALLOC_UNSHARE:
				vma_prot = pgprot_writealloc_unshare(vma_prot);
				break;
			case MT_NOCACHE_UNSHARE:
				vma_prot = pgprot_nocache_unshare(vma_prot);
				break;
			case MT_NOCACHE_SHARE:
				vma_prot = pgprot_nocache_share(vma_prot);
				break;
			default:
				break;
			}
		}
	}
#endif

	return vma_prot;
}
EXPORT_SYMBOL(phys_mem_access_prot);

static ssize_t mtrc_write(struct file *file, const char __user *buf,
				size_t len, loff_t *ppos)
{
	char line[LINE_SIZE];
	char *ptr;
	unsigned long long base = 0;
	unsigned long long size = 0;
	int err;
	unsigned int type;
	pgprot_t attr;
	int action;
	struct task_struct *tsk = NULL;
	pid_t pid = PID_GLOBAL;
	pid_t tgid;

	type = INVALID_TYPE;
	attr = __pgprot(0);

	err = mtrc_get_pram(line, buf, len);
	if (err)
		return err;

	ptr = line;

	err = mtrc_get_pid(&ptr, &pid);
	if (err) {
		pr_err("[PAT]Error:Invalid input format: %s.\n", line);
		return err;
	}

	if (pid >= 0) {
		rcu_read_lock();
		tsk = find_task_by_pid_ns(pid, &init_pid_ns);
		if (tsk == NULL) {
			pr_err("[PAT]Error:Invalid pid: %d.\n", pid);
			rcu_read_unlock();
			return -EINVAL;
		}
		tgid = task_tgid_nr(tsk);
		rcu_read_unlock();
		if (pid != tgid) {
			printk("[PAT]Info:Use tgid[%d] instead of the child thread id[%d].\n", tgid, pid);
			pid = tgid;
		}
	}

	err = mtrc_get_base(&ptr, &base);
	if (err) {
		pr_err("[PAT]Error:Invalid input format: %s.\n", line);
		return err;
	}

	err = mtrc_get_size(&ptr, &size);
	if (err) {
		pr_err("[PAT]Error:Invalid input format:%s.\n", line);
		return err;
	}

	err = mtrc_check_base_size(base, size);
	if (err)
		return err;
	size -= 1;
	action = mtrc_get_action(ptr, &type, &attr);
	switch (action) {
#if defined(CONFIG_ARM)
	case MTRC_INPUT_TYPE:
		err = mtrc_check_type(type);
		if (err) {
			pr_err("[PAT]Error: type [%d] is wrong.\n", type);
			return err;
		}
		break;
#endif
	case MTRC_INPUT_ATTR:
		if (check_attr(attr) != 0) {
			pr_err("[PAT]Error:Invalid attr[%llx].\n", (unsigned long long)pgprot_val(attr));
			return -EINVAL;
		}
		attr = update_pg_attr(attr);
		break;
	case MTRC_INPUT_DELETE:
		err = del_one_region(base, size, pid);
		return err == 0 ? len : -EINVAL;
	default:
		pr_err("[PAT]Error: Invalid input format:%s.\n", line);
		return -EINVAL;
	}

	err = add_region(base, size, type, attr, pid);
	return err < 0 ? err : len;
}

static const struct proc_ops proc_mtrc_operations = {
	.proc_open = mtrc_open,
	.proc_read = seq_read,
	.proc_write = mtrc_write,
	.proc_lseek = seq_lseek,
	.proc_release = single_release,
};
#endif

static int __init init_pat(void)
{
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry *p;
	int ret;

	p = proc_create("mtrc", S_IRUSR, NULL, &proc_mtrc_operations);
	if (p == NULL)
		return -ENOMEM;

	ret = profile_event_register(PROFILE_TASK_EXIT, &task_exit_nb);
	if (ret < 0) {
		remove_proc_entry("mtrc", NULL);
		return ret;
	}
#endif
	return  0;  /* SUCCESS */
}
late_initcall(init_pat);
